modal.js



/**
 * The X button that closes the Modal
 */
class DialogCancel extends Autowire {
    get popup(){return this.getAny('ModalPopup')}

    onclick(event){
        // console.log("Cancel Dialog");
        // console.log(this);
        // console.log("Popup:",this.popup);
        if (event.target==this.node)this.popup.hide();
    }
}
DialogCancel.autowire('.ModalPopup .DialogX');

/**
 * The full popup, this part includes the translucent overlay
 * 
 */
class ModalPopup extends DialogCancel {

    /**
     * Get an array of forms currently in the modal.
     */
    get forms(){ return this.qa('form'); }

    /**
     * set the content to an html string before calling `show()`
     */
    set content(content){
        // console.log(this.node);
        this.content_node.innerHTML = content;
    }

    get content_node(){
        return this.node.querySelector('.ModalContent');
    }

    /**
     * Simply fetch text from url
     * @param url a url 
     */
    async get(url){
        const request = new Request(url);
        const response = await fetch(request);
        const text = await response.text();
        return text;
    }
    /**
     * Simply fetch json from url
     * @param url a url 
     */
    async getJson(url){
        const request = new Request(url);
        const response = await fetch(request);
        const json= await response.json();
        return json;
    }

    /**
     * Setup a form to submit through javscript and provide a callback for the response of the submission
     * @param formNode a `<form>` node
     * @param handlerFunc a callable function that accepts `(ModalPopup, string ResponseText)`. ResponseText will be the response from submitting the form.
     */
    handleForm(formNode, handlerFunc){
        formNode.addEventListener('submit', this.formSubmitted.bind(this, handlerFunc));
    }

    /**
     * submit the form via a `Request()` object & call the handler function
     *
     * @param handlerFunc a callable that accepts `(this modal object, response_text)`
     * @param event the javascript event for a 'submit' event
     */
    async formSubmitted(handlerFunc, event){
        event.preventDefault();
        const form = event.target;
        const inputs = form.querySelectorAll('[name]');
        const params = {};
        for (const input of inputs){
            if (input.type=='file'){
                params[input.name] = input.files[0];
            } else {
                params[input.name] = input.value;
            }
        }
        // console.log(params);
        const response = await this.fetch(form.action, params, form.method);
        const text = await response.text();
        handlerFunc(this,text);
        // console.log(await response.text());
        // console.log(form);
    }

    __attach(){
        // used for [tab] navigation
        this.firstButton = this.node.querySelector('button:first-of-type');
        // used for [tab] navigation
        this.lastButton = this.node.querySelector('a:last-of-type');
        // I think this just focuses the first button in a modal when the dialog is clicked on??
        this.node.querySelector('.ModalDialog').addEventListener('click',
            function(event){
                if (event.target.tagName!='BUTTON'&&event.target.tagName!='A'){
                    this.querySelector('button').focus();
                }
            }
        );
    }

    show(){
        this.node.style.display = 'flex';
        this.node.querySelector('button').focus();
    }

    /**
     * Hide the modal & empty the content node.
     */
    hide(){
        this.node.style.display = 'none';
        // this.branchSelector.node.focus();
        this.node.querySelector('.ModalContent').innerHTML = '';
    }


    /**
     * improves accessibility for keyboard-based navigation via [tab] & [space]
     */
    onkeydown(event){
        // close if escape key
        if (event.keyCode===27){
            this.hide();
        }
        const curButton = document.activeElement;
        const isTab = (event.key === 'Tab');
        const isSpace = (event.code === 'Space' || event.code === 32);
        const isShift = event.shiftKey
        if (isTab && isShift && curButton == this.firstButton){
            this.lastButton.focus();
            event.preventDefault();
            event.stopPropagation();
        } else if (isTab && !isShift && curButton == this.lastButton){
            this.firstButton.focus();
            event.preventDefault();
            event.stopPropagation();
        } else if (isSpace && (curButton.tagName.toUpperCase()=='A'||curButton.tagName.toUpperCase()=='BUTTON')){
            // curButton.click();
            event.preventDefault();
            event.stopPropagation();
        }
    }

    /**
     * make spacebar work for clicking buttons
     */
    onkeyup(event){
        const isSpace = (event.code === 'Space' || event.code === 32);
        const curButton = document.activeElement;
        if (isSpace && (curButton.tagName.toUpperCase()=='A'||curButton.tagName.toUpperCase()=='BUTTON')){
            curButton.click();
            event.preventDefault();
            event.stopPropagation();
        }
    }
}